1.交叉熵损失函数

交叉熵H(p,q)是指按照不真实分布q来编码样本p所需的编码长度的期望。交叉熵损失是神经网络分类问题中常见的损失函数,用来判断模型对真实概率分布估计的准确程度,q(x)是预测的概率分布,p(x) 是真实的概率分布(在多分类问题的 one-hot 编码)
$$
H(p,q)=-\sum_x p(x) log\ q(x)
$$

二分类问题又可进一步展开为:
$$H(y,a)=H_y(a)=−(yloga+(1−y)log(1−a))$$

1
2
3
# y_ 真实输出值,y 预测值
y_ = tf.placeholder(tf.float32, [None, 10])
cross_ent = -tf.reduce_mean(tf.reduce_sum(y_*tf.log(y), reduce_indices=[1]))

2.TensorFlow交叉熵实现

TensorFlow中交叉熵的损失函数(loss function)主要有以下几种:

2.1 tf.nn.softmax_cross_entropy_with_logits_v2

1
2
3
4
5
6
def softmax_cross_entropy_with_logits_v2(
_sentinel=None,
labels=None,
logits=None,
dim=-1,
name=None):

Computes softmax cross entropy between logits and labels

原来的函数为nn.softmax_cross_entropy_with_logits,现在deprecated,位于nn_ops.py文件下。

softmax交叉熵loss,参数为网络最后一层的输出和onehot形式的标签。衡量独立互斥离散分类任务的误差,each entry is in exactly one class。

1
2
3
4
5
6
7
8
9
10
logits = tf.constant([[1, 2, 3], [2, 1, 3], [3, 1, 2]], tf.float64)
y = tf.nn.softmax(logits)
y_ = tf.constant([[0, 0, 1], [0, 1, 0], [0, 0, 1]], tf.float64)

cross_ent = -tf.reduce_sum(y_ * tf.log(y))
cross_ent2 = tf.reduce_sum(tf.nn.softmax_cross_entropy_with_logits_v2(logits=logits, labels=y_))

with tf.Session() as sess:
print('cross_ent: ', sess.run(cross_ent))
print('cross_ent2: ', sess.run(cross_ent2))

2.2 tf.nn.sparse_softmax_cross_entropy_with_logits

1
2
3
4
5
def sparse_softmax_cross_entropy_with_logits(
_sentinel=None,
labels=None,
logits=None,
name=None):

Computes sparse softmax cross entropy between logits and labels

这个函数和上面的区别就是labels参数应该是没有经过onehot的标签,其余的都是一样的。

2.3 tf.nn. sigmoid_cross_entropy_with_logits

1
2
3
4
5
def sigmoid_cross_entropy_with_logits(
_sentinel=None,
labels=None,
logits=None,
name=None):

Computes sigmoid cross entropy given logits

sigmoid交叉熵loss,与softmax不同的是,该函数首先进行sigmoid操作之后计算交叉熵的损失函数。衡量独立互斥离散分类任务的误差,可以处理多分类任务中的多目标检测,多标记学习等。

实现方式如下:

let x = logits, z = labels
The logistic loss = max(x, 0) - x * z + log(1 + exp(-abs(x)))

2.4 tf.nn.weighted_cross_entropy_with_logits

1
def weighted_cross_entropy_with_logits(targets, logits, pos_weight, name=None):

交叉熵损失通常定义为:
$$ loss_usual = targets * -log(sigmoid(logits)) + (1 - targets) * -log(1 - sigmoid(logits)) $$

而这种正例加权的损失函数定义为:
$$
loss_weight = targets * -log(sigmoid(logits)) * pos_weight + (1 - targets) * -log(1 - sigmoid(logits))
$$

当设置参数 pos_weight>1 时,减少了False Negative的数量,增加了召回率。
当设置参数 pos_weight<1 时,减少了False Positive的数量,增加了准确率。

实现方式如下:

let x = logits, z = targets, q = pos_weight,
setting l = (1 + (q - 1) * z)
loss = (1 - z) x + l (log(1 + exp(-abs(x))) + max(-x, 0))

3 TensorFlow自定义损失函数

TensorFlow 不仅支持经典的损失的损失函数,还可以优化任意的自定义损失函数。有两个办法:

其一、你自己用C++写一个。你需要把tensorflow的源代码下载下来,然后自己用C++写一个函数。可参考Adding an op。

其二、你把你自己定义的损失函数用tensorflow中的标准函数表示出来。比如你需要MSE(虽然这个已经有了),可以写成 loss = tf.reduce_mean(tf.square(tf.sub(y_real, y_pred)))

例子来源于《Tensorflow实战Google深度学习框架》

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
"""
使用交叉熵损失函数的神经网络分类demo
"""
import tensorflow as tf
from numpy.random import RandomState
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

batch_size=8
x=tf.placeholder(tf.float32,shape=(None,2),name='x_input')
y_=tf.placeholder(tf.float32,shape=(None,1),name='y_input')
w2=tf.Variable(tf.random_normal([3,1],stddev=1,seed=1))
w1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))
a=tf.matmul(x,w1)
y=tf.matmul(a,w2)

#定义损失函数和反向传播算法
#自定义交叉熵
cross_entropy=-tf.reduce_mean(y_*tf.log(tf.clip_by_value(y,1e-10,1.0)))
train_step=tf.train.AdamOptimizer(0.001).minimize(cross_entropy)
#模拟生成训练集
rdm=RandomState(1)
dataset_size=128
X=rdm.rand(dataset_size,2)
Y=[[int(x1+x2<1)]for (x1,x2) in X]
#0、1二分类
print(X.shape)
print(Y)

with tf.Session() as sess:
init_op=tf.global_variables_initializer()
sess.run(init_op)
print(sess.run(w1))
print(sess.run(w2))

STEPS=5000
for i in range(STEPS):
start = ( i* batch_size)%dataset_size
end= min(start+batch_size,dataset_size)
#通过选取的样本训练神经网络或训练参数
sess.run(train_step,feed_dict={x: X[start:end], y_ : Y[start:end]})

if i % 1000 == 0:
#计算每隔一段时间所有数据的交叉熵并输出
total_cross_entropy=sess.run(cross_entropy,feed_dict={x:X,y_:Y})
print("After %d training steps,cross entropy on all data is %g"%(i,total_cross_entropy))
print(sess.run(w1))
print(sess.run(w2))

对于理想的分类问题和回归问题,可采用交叉熵或者MSE损失函数,但是对于一些实际的问题,理想的损失函数可能在表达上不能完全表达损失情况,以至于影响对结果的优化。例如:对于产品销量预测问题,表面上是一个回归问题,可使用MSE损失函数。可实际情况下,当预测值大于实际值时,损失值应是正比于商品成本的函数;当预测值小于实际值,损失值是正比于商品利润的函数,多数情况下商品成本和利润是不对等的。自定义损失函数如下:
$$
Loss(y,y’)=\sum_{i=1}^n f(y_i,y’_i),
f(x,y)= \left\{
\begin{matrix}
a(x-y), x > y \\
b(y-x), x \leqslant y
\end{matrix} \right.
$$

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
"""
使用自定义损失函数的神经网络回归demo
"""
import tensorflow as tf
from numpy.random import RandomState

batch_size=8

x=tf.placeholder(tf.float32,shape=(None,2),name='x_input')
y_=tf.placeholder(tf.float32,shape=(None,1),name='y_input')
w1=tf.Variable(tf.random_normal([2,1],stddev=1,seed=1))
#预测值
y=tf.matmul(x,w1)

loss_less=10
loss_more=1
#定义损失函数和反向传播算法
#自定义损失函数
loss=tf.reduce_sum(tf.where(tf.greater(y,y_),(y-y_)*loss_more,(y_-y)*loss_less))

train_step=tf.train.AdamOptimizer(0.001).minimize(loss)
rdm=RandomState(1)

dataset_size=128
X=rdm.rand(dataset_size,2)

Y=[[x1+x2+rdm.rand()/10.0-0.05]for (x1,x2) in X]

with tf.Session() as sess:
init_op=tf.global_variables_initializer()
sess.run(init_op)
STEPS=5000
for i in range(STEPS):
start = (i * batch_size) % dataset_size
end= min(start+batch_size,dataset_size)
#通过选取的样本训练神经网络或训练参数
sess.run(train_step,feed_dict={x: X[start:end], y_ : Y[start:end]})
print(sess.run(w1))

本文地址: http://easonlv.github.io/2019/08/25/Tensorflow中自定义Loss Function/